﻿using System.Collections.Generic;
using Core.Controllers;
using Core.Model;
using Core.Model.Types;
using Core.View.Renders;
using SharpDX.D3DCompiler;
using SharpDX.DXGI;
using SharpDX.Direct3D;
using SharpDX.Direct3D10;
using Device = SharpDX.Direct3D10.Device;

namespace SharpDX.Game.View.Renders
{
    public class SharpDxSkyDomRenderer : SkyDomeRenderer
    {
        #region Fields

        private Buffer VertexBuffer;
        private Buffer IndexBuffer;

        private Device Device;

        #endregion

        #region Effect and variables

        private Effect effect;
        private EffectPass renderPass;
        private EffectMatrixVariable wvpVariable = null;
        private EffectMatrixVariable worldVariable = null;
        //private EffectMatrixVariable viewVariable = null;
        //private EffectMatrixVariable projectionVariable = null;
        private EffectVectorVariable cameraPositionVariable = null;
        private EffectVectorVariable sunPositionVariable = null;
        private EffectVectorVariable radiusInOutHeightMinMax = null;
        private EffectVectorVariable scatterFactors = null;
        private EffectVectorVariable scaleIter = null;
        private EffectVectorVariable gravity = null;
        private EffectVectorVariable invWaveLength = null;
        //private EffectScalarVariable turbidityVariable = null;

        #endregion

        #region ctor

        public SharpDxSkyDomRenderer(Device device)
        {
            Device = device;
        }

        #endregion

        #region Overriden methods

        public override void LoadContent()
        {
            var effectByteCode = ShaderBytecode.CompileFromFile("Content/Shaders/atmosphere.fx", "fx_4_0",
                                                                ShaderFlags.EnableBackwardsCompatibility,
                                                                EffectFlags.None);
            effect = new Effect(Device, effectByteCode);

            var technique = effect.GetTechniqueByIndex(0);

            renderPass = technique.GetPassByIndex(0);

            wvpVariable = effect.GetVariableByName("g_MatrixViewProj").AsMatrix();
            worldVariable = effect.GetVariableByName("g_MatrixWorld").AsMatrix();
            cameraPositionVariable = effect.GetVariableByName("g_EyePos").AsVector();
            sunPositionVariable = effect.GetVariableByName("g_LightDir").AsVector();

            radiusInOutHeightMinMax = effect.GetVariableByName("g_RadiusInOutHeightMinMax").AsVector();
            scatterFactors = effect.GetVariableByName("g_ScatterFactors").AsVector();
            scaleIter = effect.GetVariableByName("g_ScaleIter").AsVector();
            gravity = effect.GetVariableByName("g_Gravity").AsVector();
            invWaveLength = effect.GetVariableByName("g_InvWaveLength").AsVector();

            // planet inner radius, planet outer radius, height min, max
            radiusInOutHeightMinMax.Set(new Vector4(6356.75f, 6456.55f, -100.0f, 2000.0f));

            //<!--  // RayMult = 0.002f;
            //      // MieMult = 0.0015f;
            //      // KrESun = fRayMult * SunIntensity;
            //      // KmESun = fMieMult * SunIntensity;
            //      // kr4PI = fRayMult * 4.0f * PI;
            //      // km4PI = fMieMult * 4.0f * PI;
            // scattering coefficients : KrESun, KmESun, Kr4PI, Km4PI
            scatterFactors.Set(new Vector4(0.04f, 0.03f, 0.025f, 0.188f));

            // Scale = 1 / (fOuterRadius - fInnerRadius)
            // ScaleDepth  = (fOuterRadius - fInnerRadius)
            // ScaleOverScaleDepth = Scale / ScaleDepth
            // Iterations = 10
            scaleIter.Set(new Vector4(0.01f, 0.25f, 0.04f, 2.0f));

            // g
            // g*g
            gravity.Set(new Vector4(-0.991f, 0.982f, 0f, 0f));

            // r = 650 nm
            // g = 570 nm
            // b = 475 nm
            // InvWaveLength = 1.0 / powf( x, 4.0f );
            invWaveLength.Set(new Vector4(5.602f, 9.473f, 19.644f, 0f));

            List<int> indicies;
            List<SkyDomeVertex> vertexes;

            Calculate(out vertexes, out indicies);

            int indexSizeInBytes = IndexCount*sizeof (int);

            using (DataStream indexDataStream = new DataStream(indexSizeInBytes, true, true))
            {
                indexDataStream.WriteRange(indicies.ToArray());

                indexDataStream.Position = 0;

                IndexBuffer = new Buffer(Device, indexDataStream, new BufferDescription()
                                                                      {
                                                                          BindFlags = BindFlags.IndexBuffer,
                                                                          CpuAccessFlags = CpuAccessFlags.None,
                                                                          OptionFlags = ResourceOptionFlags.None,
                                                                          SizeInBytes = indexSizeInBytes,
                                                                          Usage = ResourceUsage.Default
                                                                      });
            }

            int vertexSizeInBytes = vertexes.Count*SkyDomeVertex.SizeInBytes;

            using (DataStream vertexDataStream = new DataStream(vertexSizeInBytes, true, true))
            {
                vertexDataStream.WriteRange(vertexes.ToArray());

                vertexDataStream.Position = 0;

                VertexBuffer = new Buffer(Device, vertexDataStream, new BufferDescription()
                                                                        {
                                                                            BindFlags = BindFlags.VertexBuffer,
                                                                            CpuAccessFlags = CpuAccessFlags.None,
                                                                            OptionFlags = ResourceOptionFlags.None,
                                                                            SizeInBytes = vertexSizeInBytes,
                                                                            Usage = ResourceUsage.Default
                                                                        });
            }
        }

        private int ts;

        public override void Draw(Camera camera)
        {
            ts++;

            MatrixF world = camera.World;// MatrixF.CreateTranslation(camera.Position.X, camera.Position.Y, camera.Position.Z);

            wvpVariable.SetMatrix(world * camera.View * camera.Projection);

            worldVariable.SetMatrix(world);

            cameraPositionVariable.Set(camera.Position);
            sunPositionVariable.Set(camera.CameraWorld.SkyDomeRenderer.GetDirection());

            Device.InputAssembler.SetIndexBuffer(IndexBuffer, Format.R32_UInt, 0);

            Device.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            Device.InputAssembler.SetVertexBuffers(0,
                                                   new VertexBufferBinding(VertexBuffer,
                                                                           SkyDomeVertex.SizeInBytes, 0));

            renderPass.Apply();

            Device.DrawIndexed(IndexCount, 0, 0);
        }

        #endregion
    }
}